package CompilerSPy;

import java.util.ArrayList;
import java.util.List;

import AST.*;

import Lexer.*;

public class Compiler {

	// compile must receive an input with an character less than
	// p_input.lenght
	public Program compile(char[] input) {

		symbolTable = new SymbolTable();
		error = new CompilerError(null);
		lexer = new Lexer(input);

		lexer.nextToken();
		Stmt st = new Stmt() {
		};
		while (lexer.token != Symbol.EOF) {
			st = stmt();
		}
		Program p = new Program(st);

		return p;
	}

	public Stmt stmt() {
		// stmt: simple_stmt | compound_stmt
		if (isCompoundStmt()) {
			return compound_stmt();
		} else if (isSimpleStmt()) {
			return simple_stmt();
		} else {
			error.show("Simple or Compound stmt expected!");
		}
		return null;
	}

	public SimpleStmt simple_stmt() {
		// simple_stmt: small_stmt (';' small_stmt)* NEWLINE

		List<SmallStmt> st = new ArrayList<SmallStmt>();
		st.add(small_stmt());

		while (lexer.token == Symbol.SEMICOLON) {
			lexer.nextToken();
			st.add(small_stmt());
		}

		if (lexer.token != Symbol.NEWLINE && lexer.token != Symbol.EOF) {
			error.show("NEWLINE expected");
		}

		return new SimpleStmt(st);
	}

	public SmallStmt small_stmt() {
		// small_stmt: (expr_stmt | print_stmt | flow_stmt)
		SmallStmt st;

		if (isPrintStmt()) {
			st = print_stmt();
		} else if (isFlowStmt()) {
			st = flow_stmt();
		} else {
			st = expr_stmt();
		}

		return st;
	}

	public ExprStmt expr_stmt() {
		// expr_stmt: targetlist augassign listmaker
		ExprStmt eS = new ExprStmt();

		eS.setTargetList(targetList());
		// lexer.nextToken();
		eS.setAugAssign(augAssign());
		// lexer.nextToken();
		eS.setListmaker(listmaker());

		return eS;
	}

	public AugAssign augAssign() {
		// augassign: ('=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' |
		// '^=')
		AugAssign aug = new AugAssign();
		if (isAugAssign()) {
			aug.setOp(lexer.token);
			lexer.nextToken();
		} else {
			error.show("Assign symbol expected");
		}
		return aug;
	}

	public TargetList targetList() {
		// targetlist = target ("," target)*

		List<Target> tg = new ArrayList<Target>();
		tg.add(target());

		while (lexer.token == Symbol.COMMA) {
			lexer.nextToken();
			tg.add(target());
		}

		return new TargetList(tg);
	}

	public Target target() {
		// target = NAME
		Target tg = new Target();

		if (lexer.token == Symbol.NAME) {
			tg.setName(new SPyName(lexer.getStringValue()));
			lexer.nextToken();
		}

		return tg;
	}

	public PrintStmt print_stmt() {
		// print_stmt: 'print' ( test (',' test)* )
		List<Test> ts = new ArrayList<Test>();

		if (isPrintStmt()) {

			lexer.nextToken();
			ts.add(test());
			while (lexer.token == Symbol.COMMA) {
				lexer.nextToken();

			}
		} else {
			error.show("Print expected");
		}

		return new PrintStmt(ts);
	}

	public FlowStmt flow_stmt() {
		// flow_stmt: break_stmt | continue_stmt | return_stmt
		FlowStmt fs = null;
		if (isBreakStmt()) {
			fs = break_stmt();
		}
		if (isContinueStmt()) {
			fs = continue_stmt();
		}
		if (isReturnStmt()) {
			fs = return_stmt();
		}
		if (!isBreakStmt() || !isContinueStmt() || !isReturnStmt()) {
			error.show("Break, continue or return statement expected");
		}

		return fs;
	}

	public BreakStmt break_stmt() {
		// break_stmt: 'break'
		// lexer.token = Symbol.BREAK;
		return new BreakStmt();
	}

	public ContinueStmt continue_stmt() {
		// continue_stmt: 'continue'
		// lexer.token = Symbol.CONTINUE;
		return new ContinueStmt();
	}

	public ReturnStmt return_stmt() {
		// return_stmt: 'return' test
		// lexer.token = Symbol.RETURN;
		ReturnStmt rs = new ReturnStmt();
		lexer.nextToken();
		rs.setTest(test());
		return rs;
	}

	public CompoundStmt compound_stmt() {
		// compound_stmt: if_stmt | while_stmt | for_stmt | funcdef | classdef
		CompoundStmt cs = null;
		if (isIfStmt()) {
			cs = if_stmt();
		} else if (isWhileStmt()) {
			cs = while_stmt();
		} else if (isForStmt()) {
			cs = for_stmt();
		} else if (isFuncDef()) {
			cs = funcdef();
		} else if (isClassDef()) {
			cs = classdef();
		} else {
			error.show("Conditional statement expected");
		}

		return cs;
	}

	public IfStmt if_stmt() {
		// if_stmt: 'if' test ':' suite ('elif' test ':' suite)* ['else' ':'
		// suite]
		IfStmt ifS = new IfStmt();

		lexer.nextToken();
		ifS.setIfTest(test());

		if (lexer.token == Symbol.COLON) {
			lexer.nextToken();
			ifS.setIfSuit(suite());
		} else {
			error.show("COLON expected");
		}

		lexer.nextToken();

		while (lexer.token == Symbol.ELIF) {
			lexer.nextToken();
			ifS.addElifTests(test());
			if (lexer.token == Symbol.COLON) {
				lexer.nextToken();
				ifS.addElifSuites(suite());
				lexer.nextToken();
			} else {
				error.show("COLON expected");
			}
		}

		if (lexer.token == Symbol.ELSE) {
			lexer.nextToken();
			if (lexer.token == Symbol.COLON) {
				lexer.nextToken();
				ifS.setElseSuite(suite());
			} else {
				error.show("COLON expected");
			}
		}
		return ifS;
	}

	public WhileStmt while_stmt() {
		// while_stmt: 'while' test ':' suite ['else' ':' suite]
		WhileStmt wS = new WhileStmt();

		lexer.nextToken();
		wS.setWhileTest(test());

		if (lexer.token == Symbol.COLON) {
			lexer.nextToken();
			wS.setWhileSuite(suite());
			lexer.nextToken();
			if (lexer.token == Symbol.ELSE) {
				lexer.nextToken();
				if (lexer.token == Symbol.COLON) {
					lexer.nextToken();
					wS.setElseSuite(suite());
				} else {
					error.show("COLON expected");
				}
			}
		} else {
			error.show("COLON expected");
		}
		return wS;
	}

	public ForStmt for_stmt() {
		// for_stmt: 'for' exprlist 'in' atom ':' suite ['else' ':' suite] |
		// 'for' exprlist 'in' 'range' '(' NUMBER ',' NUMBER ')' ':' suite
		// ['else' ':' suite]
		ForStmt forS = new ForStmt();

		//lexer.nextToken();
		forS.setExprList(exprList());
		lexer.nextToken();
		if (lexer.token == Symbol.IN) {
			lexer.nextToken();
			if (lexer.token == Symbol.RANGE) {
				// 'for' exprlist 'in' 'range' '(' NUMBER ',' NUMBER ')' ':'
				// suite ['else' ':' suite]
				lexer.nextToken();
				if (lexer.token == Symbol.LEFTPAR) {
					lexer.nextToken();
					forS.setNum1(number());
					lexer.nextToken();
					if (lexer.token == Symbol.COMMA) {
						lexer.nextToken();
						forS.setNum2(number());
						lexer.nextToken();
						if (lexer.token == Symbol.RIGHTPAR) {
							lexer.nextToken();
							if (lexer.token == Symbol.COLON) {
								lexer.nextToken();
								forS.setForSuite(suite());
								lexer.nextToken();
								if (lexer.token == Symbol.ELSE) {
									lexer.nextToken();
									if (lexer.token == Symbol.COLON) {
										lexer.nextToken();
										forS.setElseSuite(suite());
									} else {
										error.show("COLON expected");
									}
								}
							} else {
								error.show("COLON expected");
							}

						} else {
							error.show("RIGHTPAR expected");
						}
					} else {
						error.show("COMMA expected");
					}
				} else {
					error.show("LEFTPAR expected");
				}
			} else {
				// 'for' exprlist 'in' atom ':' suite ['else' ':' suite]
				forS.setAtom(atom());
				lexer.nextToken();
				if (lexer.token == Symbol.COLON) {
					lexer.nextToken();
					forS.setForSuite(suite());
					lexer.nextToken();
					if (lexer.token == Symbol.ELSE) {
						lexer.nextToken();
						if (lexer.token == Symbol.COLON) {
							lexer.nextToken();
							forS.setElseSuite(suite());
						} else {
							error.show("COLON expected");
						}
					}
				} else {
					error.show("COLON expected");
				}
			}
		} else {
			error.show("IN expected");
		}
		return forS;
	}

	public SPyNumber number() {
		// # NUMBER: int ou float
		if (lexer.token == Symbol.NUMBER) {
			SPyNumber number = new SPyNumber();
			if (lexer.getStringValue() == "int") {
				number.setSpyInt(lexer.getNumberValue());
			}
			if (lexer.getStringValue() == "float") {
				number.setSpyFloat((float) lexer.getNumberValue());
			}
			return number;
		}
		return null;
	}

	public Exprlist exprList() {
		// Expr+
		Exprlist eL = new Exprlist();

		eL.addExprList(expr());

		return eL;
	}

	public FuncDef funcdef() {
		// funcdef: 'def' NAME parameters ':' suite
		FuncDef func = new FuncDef();
		// semantics analysis
		String name = (String) lexer.getStringValue();
		symbolTable.putInLocal(name, func);

		lexer.nextToken();
		if (lexer.token == Symbol.ID) {
			lexer.nextToken();
			func.setParameters(parameters());
			if (lexer.token == Symbol.COLON) {
				lexer.nextToken();
				func.setSuite(suite());
			} else {
				error.show("COLON expected");
			}
		} else {
			error.show("ID expected");
		}
		return func;
	}

	public ClassDef classdef() {
		// classdef: 'class' NAME ['(' [atom [',' atom]* ] ')'] ':' suite
		ClassDef classDef = new ClassDef();

		lexer.nextToken();
		if (lexer.token == Symbol.NAME) {
			classDef.setName(new SPyName(lexer.getStringValue()));
			// semantics analysis
			String name = (String) lexer.getStringValue();
			symbolTable.putInLocal(name, classDef);

			if (lexer.token == Symbol.LEFTPAR) {
				lexer.nextToken();
				if (lexer.token == Symbol.RIGHTPAR) {
					lexer.nextToken();
				} else {
					classDef.setAtom(atom());
					lexer.nextToken();
					while (lexer.token == Symbol.COMMA) {
						lexer.nextToken();
						classDef.addAtomList(atom());
					}
					if (lexer.token == Symbol.RIGHTPAR) {
						lexer.nextToken();
					} else {
						error.show("RIGHTPAR expected");
					}
				}
			}
			if (lexer.token == Symbol.COLON) {
				lexer.nextToken();
				classDef.setSuite(suite());
			}
		} else {
			error.show("ID expected");
		}

		return classDef;
	}

	public Parameters parameters() {
		// parameters: '(' [varargslist] ')'
		Parameters par = new Parameters();
		if (lexer.token == Symbol.LEFTPAR) {
			lexer.nextToken();
			if (lexer.token == Symbol.RIGHTPAR) {
				lexer.nextToken();
				return par;
			} else {
				par.setVarArgsList(varArgsList());
				lexer.nextToken();
				if (lexer.token == Symbol.RIGHTPAR) {
					lexer.nextToken();
				} else {
					error.show("RIGHTPAR expected");
				}
			}
		} else {
			error.show("LEFTPAR expected");
		}

		return par;
	}

	public VarArgsList varArgsList() {
		// varargslist: ([fpdef ['=' test] (',' fpdef ['=' test])* ] )

		VarArgsList vAL = new VarArgsList();

		vAL.setFpdef(fpdef());
		if (lexer.token == Symbol.ASSIGN) {
			lexer.nextToken();
			vAL.setTest(test());
			while (lexer.token == Symbol.COMMA) {
				lexer.nextToken();
				vAL.addFpdefList(fpdef());
				if (lexer.token == Symbol.ASSIGN) {
					lexer.nextToken();
					vAL.addTestList(test());
				}
			}
		}
		return vAL;
	}

	public FpDef fpdef() {
		// fpdef: NAME | '(' fplist ')'
		FpDef fp = new FpDef();
		if (lexer.token == Symbol.NAME) {
			fp.setName(new SPyName(lexer.getStringValue()));
			// semantics analysis
			String name = (String) lexer.getStringValue();
			symbolTable.putInLocal(name, fp);

			lexer.nextToken();
			return fp;
		} else if (lexer.token == Symbol.LEFTPAR) {
			fp.setFpList(fplist());
			lexer.nextToken();
			if (lexer.token == Symbol.RIGHTPAR) {
				lexer.nextToken();
			} else {
				error.show("RIGHTPAR expected");
			}
		} else {
			error.show("NAME or LEFTPAR expected");
		}
		return fp;
	}

	public FpList fplist() {
		// fplist: fpdef (',' fpdef)*

		List<FpDef> fpD = new ArrayList<FpDef>();
		fpD.add(fpdef());

		while (lexer.token == Symbol.COMMA) {
			lexer.nextToken();
			fpD.add(fpdef());
		}

		return new FpList(fpD);
	}

	public Test test() {
		// test: or_test ['if' or_test 'else' test]
		Test oT = new Test();
		oT.setOrTest(or_test());
		lexer.nextToken();

		return oT;
	}

	public OrTest or_test() {
		// or_test: and_test ('or' and_test)*
		List<AndTest> aT = new ArrayList<AndTest>();
		aT.add(and_test());

		while (lexer.token == Symbol.OR) {
			lexer.nextToken();
			aT.add(and_test());
		}

		return new OrTest(aT);
	}

	public AndTest and_test() {
		// and_test: not_test ('and' not_test)*
		List<NotTest> nT = new ArrayList<NotTest>();
		nT.add(not_test());

		while (lexer.token == Symbol.AND) {
			lexer.nextToken();
			nT.add(not_test());
		}

		return new AndTest(nT);
	}

	public NotTest not_test() {
		// not_test: 'not' not_test | comparison
		NotTest nT = new NotTest();
		if (lexer.token == Symbol.NOT) {
			lexer.nextToken();
			nT.setNotTest(not_test());
		} else {
			nT.setComparison(comparison());
		}

		return nT;
	}

	public Comparison comparison() {
		// comparison: expr (comp_op expr)*
		Comparison c = new Comparison();
		// List<Expr> e = new ArrayList<Expr>();
		c.addExprList(expr());
		lexer.nextToken();

		while (isCompOP()) {
			c.addCompOp(comp_op());
			c.addExprList(expr());
			lexer.nextToken();
		}

		return c;
	}

	public CompOp comp_op() {
		// comp_op: '<'|'>'|'=='|'>='|'<='|'<>'|'!='|'in'|'not' 'in'|'is'|'is'
		// 'not'
		CompOp cp = new CompOp();
		if (isCompOP()) {
			if (lexer.token == Symbol.NOT) {
				lexer.nextToken();
				if (lexer.token == Symbol.IN) {
					lexer.nextToken();
				}
				cp.setCompOp(Symbol.NOTIN);
			} else if (lexer.token == Symbol.IS) {
				lexer.nextToken();
				if (lexer.token == Symbol.NOT) {
					lexer.nextToken();
				}
				cp.setCompOp(Symbol.ISNOT);
			} else {
				cp.setCompOp(lexer.token);
				lexer.nextToken();
			}
		} else {
			error.show("Comparison operator expected");
		}

		return cp;
	}

	public Expr expr() {
		// expr: xor_expr ('|' xor_expr)*

		List<XorExpr> xExp = new ArrayList<XorExpr>();
		xExp.add(xor_expr());

		while (lexer.token == Symbol.ORSYMBOL) {
			lexer.nextToken();
			xExp.add(xor_expr());
		}

		return new Expr(xExp);
	}

	public XorExpr xor_expr() {
		// xor_expr: and_expr ('^' and_expr)*

		List<AndExpr> aExp = new ArrayList<AndExpr>();
		aExp.add(and_expr());

		while (lexer.token == Symbol.XOR) {
			lexer.nextToken();
			aExp.add(and_expr());
		}

		return new XorExpr(aExp);
	}

	public AndExpr and_expr() {
		// and_expr: arith_expr ('&' arith_expr)*

		List<ArithExpr> arExp = new ArrayList<ArithExpr>();
		arExp.add(arith_expr());

		while (lexer.token == Symbol.ANDSYMBOL) {
			lexer.nextToken();
			arExp.add(arith_expr());
		}

		return new AndExpr(arExp);
	}

	public ArithExpr arith_expr() {
		// arith_expr: term (('+'|'-') term)*
		ArithExpr aExpr = new ArithExpr();
		aExpr.addTermList(term());

		while (lexer.token == Symbol.PLUS || lexer.token == Symbol.MINUS) {
			aExpr.addOpList(lexer.token);
			lexer.nextToken();
			aExpr.addTermList(term());
		}

		return aExpr;
	}

	public Term term() {
		// term: factor (('*'|'/'|'%'|'//') factor)*
		Term term = new Term();

		term.addFactorList(factor());

		while (lexer.token == Symbol.MULT || lexer.token == Symbol.DIV
				|| lexer.token == Symbol.MOD || lexer.token == Symbol.FLOORDIV) {
			term.addOpList(lexer.token);
			lexer.nextToken();
			term.addFactorList(factor());
		}

		return term;
	}

	public Factor factor() {
		// factor: ('+'|'-'|'~') factor | atom
		Factor f = new Factor();
		if (lexer.token == Symbol.PLUS || lexer.token == Symbol.MINUS
				|| lexer.token == Symbol.INVERTION) {
			f.setMod(lexer.token);
			lexer.nextToken();
			f.setFactor(factor());

		} else {

			f.setAtom(atom());
		}

		return f;
	}

	public Atom atom() {
		// atom: '[' [listmaker] ']' | NAME | NUMBER | STRING+
		Atom at = new Atom();

		if (lexer.token == Symbol.LEFTCURBRACKET) {
			lexer.nextToken();
			if (lexer.token == Symbol.RIGHTCURBRACKET) {
				lexer.nextToken();
			} else {
				at.setListmaker(listmaker());
				lexer.nextToken();
				if (lexer.token == Symbol.RIGHTCURBRACKET) {
					lexer.nextToken();
					return at;
				} else {
					error.show("RIGHTCURBRACKET expected");
				}
			}
		} else if (lexer.token == Symbol.NAME) {
			// semantics analysis
			String name = (String) lexer.getStringValue();
			symbolTable.putInLocal(name, at);

			at.setName(name());
			lexer.nextToken();
		} else if (lexer.token == Symbol.NUM) {
			at.setNumber(number());
			lexer.nextToken();
		} else if (lexer.token == Symbol.STRING) {
			at.setString(null);
			lexer.nextToken();
		} else {
			error.show("atom expected");
		}
		return at;
	}

	public SPyName name() {

		if (lexer.token == Symbol.NAME) {
			SPyName name = new SPyName(lexer.getStringValue());
			// semantics analysis
			String nameSPy = (String) lexer.getStringValue();
			symbolTable.putInLocal(nameSPy, name);

			return name;
		}
		return null;
	}

	public Listmaker listmaker() {
		// listmaker: test (',' test)*
		List<Test> ts = new ArrayList<Test>();
		ts.add(test());

		while (lexer.token == Symbol.COMMA) {
			lexer.nextToken();
			ts.add(test());
		}

		return new Listmaker(ts);
	}

	public Suite suite() {
		// suite: simple_stmt | NEWLINE INDENT stmt+ DEDENT
		Suite s = new Suite();

		if (lexer.token == Symbol.NEWLINE) {
			lexer.nextToken();
			if (lexer.token == Symbol.INDENT) {
				lexer.nextToken();
				do {
					s.addStmtList(stmt());
					lexer.nextToken();
				} while (lexer.token != Symbol.DEDENT);
				if (lexer.token == Symbol.DEDENT) {
					lexer.nextToken();
				} else {
					error.show("DEDENT expected");
				}
			} else {
				error.show("INDENT expected");
			}
		} else {
			s.setSimpleStmt(simple_stmt());
		}
		return s;
	}

	public boolean isAugAssign() {
		if (lexer.token == Symbol.ASSIGN || lexer.token == Symbol.PLUSASSIGN
				|| lexer.token == Symbol.MINUSASSIGN
				|| lexer.token == Symbol.MULTIASSIGN
				|| lexer.token == Symbol.DIVASSIGN
				|| lexer.token == Symbol.MODASSIGN
				|| lexer.token == Symbol.ANDASSIGN
				|| lexer.token == Symbol.ORASSIGN
				|| lexer.token == Symbol.XORASSIGN) {
			return true;
		}
		return false;
	}

	public boolean isCompOP() {
		if (lexer.token == Symbol.LT || lexer.token == Symbol.GT
				|| lexer.token == Symbol.EQ || lexer.token == Symbol.GE
				|| lexer.token == Symbol.LE || lexer.token == Symbol.NEQ
				|| lexer.token == Symbol.NEQC || lexer.token == Symbol.IN
				|| lexer.token == Symbol.NOT || lexer.token == Symbol.IS) {
			return true;
		}
		return false;
	}

	public boolean isSimpleStmt() {
		if (lexer.token == Symbol.NAME || isPrintStmt() || isBreakStmt()
				|| isContinueStmt() || isReturnStmt()) {
			return true;
		}
		return false;
	}

	public boolean isCompoundStmt() {
		if (isIfStmt() || isWhileStmt() || isForStmt() || isFuncDef()
				|| isClassDef()) {
			return true;
		}
		return false;

	}

	public boolean isIfStmt() {
		if (lexer.token == Symbol.IF) {
			return true;
		}
		return false;
	}

	public boolean isWhileStmt() {
		if (lexer.token == Symbol.WHILE) {
			return true;
		}
		return false;
	}

	public boolean isForStmt() {
		if (lexer.token == Symbol.FOR) {
			return true;
		}
		return false;
	}

	public boolean isFuncDef() {
		if (lexer.token == Symbol.DEF) {
			return true;
		}
		return false;
	}

	public boolean isClassDef() {
		if (lexer.token == Symbol.CLASS) {
			return true;
		}
		return false;
	}

	public boolean isBreakStmt() {
		if (lexer.token == Symbol.BREAK) {
			return true;
		}
		return false;
	}

	public boolean isContinueStmt() {
		if (lexer.token == Symbol.CONTINUE) {
			return true;
		}
		return false;
	}

	public boolean isReturnStmt() {
		if (lexer.token == Symbol.RETURN) {
			return true;
		}
		return false;
	}

	public boolean isFlowStmt() {
		if (isBreakStmt() || isContinueStmt() || isReturnStmt()) {
			return true;
		}
		return false;
	}

	public boolean isPrintStmt() {
		if (lexer.token == Symbol.PRINT) {
			return true;
		}
		return false;
	}

	private SymbolTable symbolTable;
	public static Lexer lexer;
	private CompilerError error;
}